Pythonで選挙データを分析してみよう!⑥〜ライバル候補との直接対決編〜

このシリーズでは、2025/07/21に行われた参院選挙結果を分析するためのプログラムを、一つずつ紐解いて解説していきます。
選挙データ分析シリーズ第6弾、今回はさらに一歩踏み込んで「比較分析」の世界に足を踏み入れます。

作成したコードは Github に公開しているので、興味があれば、見てみてください。
※コードは、予告なく、改変/削除されることがあります。

前回は一人の候補者(と政党)を多角的に分析しましたが、選挙は相対的な競争です。そこで今回は、特定の候補者同士を直接比較し、どの地域で、どのように競り合ったのかを可視化する detailed_comparison_analysis_csv.py スクリプトを解説していきます。

分析の目的:ライバルとの差を知る

今回の分析対象は、前回に引き続き「武藤 かず子」候補(チームみらい)。そして、比較対象として「古川 俊治」候補(自由民主党)と「江原 くみ子」候補(国民民主党)の二人を立てます。

このスクリプトのゴールは、

  • 3者の総合的な得票力はどれくらい違うのか?
  • 市区町村レベルで見たとき、誰が優勢だったのか?
  • 「武藤」候補が善戦した地域、逆に差をつけられた地域はどこか?

といった、より戦略的な問いに答えるためのインサイトを得ることです。

Step 1: 比較対象のデータを抽出する

まずは、分析したい3名の候補者のデータを、整形済みのCSVからそれぞれ抜き出します。

def extract_candidate_electoral_data(candidates_df, voting_info_df, candidate_name):
    """
    CSVデータから指定候補者の情報を抽出(総投票数付き)
    """
    # 候補者名でデータをフィルタリング
    candidate_data = candidates_df[candidates_df['候補者名'] == candidate_name].copy()
    
    if candidate_data.empty:
        return []
    
    # 投票情報データとマージして、市区町村ごとの「投票者数計」を取得
    merged_data = candidate_data.merge(
        voting_info_df[['市区町村名', '投票者数計']], 
        on='市区町村名', 
        how='left'
    )
    
    # 投票者数に対する得票率を計算
    merged_data['得票率'] = (merged_data['得票数'] / merged_data['投票者数計']) * 100
    
    # ... データを整形して返す ...
    return electoral_data

# --- main関数内での呼び出し ---
team_mirai_data = extract_candidate_electoral_data(candidates_df, voting_info_df, '武藤 かず子')
furukawa_data = extract_candidate_electoral_data(candidates_df, voting_info_df, '古川 俊治')
ehara_data = extract_candidate_electoral_data(candidates_df, voting_info_df, '江原 くみ子')

前回の分析と同様に、merge を使って市区町村ごとの総投票数を紐づけ、投票者数に対する得票率を算出しています。ここでの得票率は、後の比較分析における最も重要な指標となります。

Step 2: 地域ごとの勝敗を判定する

3者のデータが揃ったら、いよいよ直接対決です。レポートを生成する関数の中で、非常に興味深い処理が行われています。

def generate_detailed_comparison_report(team_mirai_data, furukawa_data, ehara_data):
    # ...
    tm_df = pd.DataFrame(team_mirai_data)
    fk_df = pd.DataFrame(furukawa_data)
    eh_df = pd.DataFrame(ehara_data)
    
    combined_data = []
    # 武藤候補のデータ(市区町村)を基準にループ
    for _, tm_row in tm_df.iterrows():
        municipality = tm_row['municipality']
        tm_votes = tm_row['votes']
        
        # 同じ市区町村の、他の候補者の得票数を取得
        fk_votes = fk_df[fk_df['municipality'] == municipality]['votes'].iloc[0]
        eh_votes = eh_df[eh_df['municipality'] == municipality]['votes'].iloc[0]

        # 3者の得票数をリストに格納
        votes_list = [(tm_votes, 'チームみらい'), (fk_votes, '自民党'), (eh_votes, '国民民主')]
        # 得票数が多い順に並び替え
        votes_list.sort(key=lambda x: x[0], reverse=True)
        
        # 武藤候補が何位だったかを計算
        tm_rank = next((i+1 for i, (votes, party) in enumerate(votes_list) if party == 'チームみらい'), 4)
        
        combined_data.append({
            'municipality': municipality,
            'tm_votes': tm_votes,
            'tm_rank': tm_rank,
            # ... 他のデータも格納 ...
        })
    
    combined_df = pd.DataFrame(combined_data)
    # ...

このコードのポイントは、市区町村ごとに3者の得票数をリストにまとめ、sortで順位付けしている点です。これにより、「この町では武藤さんが1位だった」「あそこの市では3位だった」という、地域ごとの詳細な勝敗を明らかにできます。

Step 3: 激戦区を見つけ出す

選挙戦において、圧勝した地域や大敗した地域だけでなく、「あと一歩で勝てたかもしれない」あるいは「僅差で勝った」という激戦区を特定することは非常に重要です。このスクリプトでは、そのための分析も行っています。

        # ... combined_data を作成するループの中 ...
        # 武藤候補の得票率と、他の2候補の得票率との差をそれぞれ計算し、
        # より差が小さい方を「最小差」として記録
        'min_diff': min(abs(tm_rate - fk_rate), abs(tm_rate - eh_rate))
    # ...

# ... レポート生成の最後 ...
# 最小差でソートして、激戦だった地域をリストアップ
sorted_by_diff = combined_df.sort_values('min_diff', ascending=True)

report += "他候補との得票率差が最小だった地域(全地域降順):\n"
for i, (_, row) in enumerate(sorted_by_diff.iterrows(), 1):
    report += f"  {i:2d}. {row['municipality']}: 最小差{row['min_diff']:.2f}%\n"

各市区町村で、武藤候補と他の2候補の得票率の差を計算し、その差が最も小さい min_diff を算出しています。そして、レポートの最後でこの min_diff が小さい順に並び替えることで、「どの地域が最も競り合っていたか」が一目瞭然になります。

まとめ

今回は、複数の候補者のデータを突き合わせることで、より戦略的な示唆を得るための比較分析の手法を見てきました。

  • 分析の基本は、同じ土俵(今回は市区町村ごと)で数値を比較すること。
  • 単純な得票数だけでなく、得票率や順位といった指標を用いることで、より本質的な強さ・弱さが見えてくる。
  • 候補者間の「差」に着目することで、選挙戦の鍵となった「激戦区」を特定できる。

個別の分析から一歩進んだ比較分析を行うことで、データは単なる結果報告ではなく、次の戦略を立てるための羅針盤となり得ます。